﻿namespace LizardChess
open System
open System.Windows.Forms
open System.Drawing
open System.Linq
open System.IO


module main =
    do
        Application.EnableVisualStyles()
        Application.SetCompatibleTextRenderingDefault(false)
    let form=
        // UCIWrapper bits
        let settings = Eng.loadSettings()
        let proc = new System.Diagnostics.Process()
        let lastmsg = ref ""
        let lastmsg1 = ref ""
        // others
        let args = Environment.GetCommandLineArgs()
        let vnname = args.[2]
        let vnisw = (args.[1] = "W")
        let doAll = (args.[3].ToUpper() = "ALL")
        let curdepth = ref settings.mindepth
        let ln = ref ""
        let lnct = ref 0
        let mvct = ref -1
        let lnnum = ref -1
        let vn = 
            let tmp = Varn.loadtxt(vnname, vnisw)
            if (args.Length = 5) then 
                lnnum := Convert.ToInt32(args.[4])
                //if have passed in a line number then just limit variation
                if  not (!lnnum = -1) then [| tmp.[!lnnum] |] else tmp
            else tmp
        let linestore = Eng.loadLineStore()
        // main form
        let frm=new Form(AutoScaleDimensions = new SizeF(6.0f, 13.0f),AutoScaleMode = AutoScaleMode.Font,ClientSize = new Size(909, 271),Icon = new Icon("lizard_icon_blue.ico"),Name = "frmEng",Text = "Analyse Form")
        // columnTime
        let columnTime = new ColumnHeader(Text = "Time")
        // columnAnswer
        let columnAnswer = new ColumnHeader(Text = "Answer",Width = 798)
        // engListView
        let engListView = new System.Windows.Forms.ListView(Dock = DockStyle.Fill,Location = new Point(0, 0),Name = "engListView",Size = new Size(909, 225),UseCompatibleStateImageBehavior = false,View = View.Details)
        engListView.Columns.AddRange([|columnTime;columnAnswer|])
        frm.Controls.Add(engListView)
        // helpProvider1
        let helpProvider1 = new HelpProvider(HelpNamespace = "LizardChess.chm")
        helpProvider1.SetHelpKeyword(frm, "EngineAnalyser.htm");
        helpProvider1.SetHelpNavigator(frm, HelpNavigator.Topic)
        helpProvider1.SetHelpString(frm, "EngineAnalyser.htm")
        helpProvider1.SetShowHelp(frm, true)
        // panel1
        let panel1 = new Panel(Dock = DockStyle.Bottom,Location = new Point(0, 225),Name = "kryptonPanel1",Size = new Size(909, 46))
        frm.Controls.Add(panel1)
        // helpButton
        let helpButton = new Button(Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Right),Location = new Point(820, 9),Name = "helpButton",Size = new Size(77, 25),TabIndex = 3,Text = "Help")
        panel1.Controls.Add(helpButton)
        // msglabel
        let msgLabel = new Label(Name = "msgLabel",Location = new Point(12, 14),Size = new Size(761, 20),TabIndex = 2,Text = "Engine Messages will be displayed here")
        panel1.Controls.Add(msgLabel)



        //add functions
        let setmsg(msg) =
            if (frm.InvokeRequired) then
                if (frm.Visible) then
                    frm.Invoke(MethodInvoker(fun () -> msgLabel.Text <- msg)) |> ignore
            else
                msgLabel.Text <- msg
        let clear() =
            let doclear() =
                //only clear if greater than 1000 items
                if (engListView.Items.Count>1000) then engListView.Items.Clear()
            if (frm.InvokeRequired) then
                if (frm.Visible) then
                    frm.Invoke(MethodInvoker(fun () -> doclear() )) |> ignore
            else
                doclear()
        let setinfo(msg) =
            let doinfo() =
                let item = engListView.Items.Add(new ListViewItem([| DateTime.Now.ToString("T"); msg |]))
                item.EnsureVisible()
            if (frm.InvokeRequired) then
                if (frm.Visible) then
                    frm.Invoke(MethodInvoker(fun () -> doinfo() )) |> ignore
            else
                doinfo()
        let Log(message:string) =
            if (settings.log) then
                use w = new StreamWriter(Eng.logfile, true)
                w.WriteLine(message)
                w.Close()
        let Send(command) =
            if (settings.log) then Log(command)
            proc.StandardInput.WriteLine(command)
        let ComputeAnswer(mvs, depth) =
            Send("ucinewgame")
            Send("position startpos")
            let mutable msg = "position startpos moves"
            for mv in mvs do
                msg <- msg + " " + mv
            Send(msg)
            if (depth > 0) then
                Send("go depth " + depth.ToString())
            else
                Send("go infinite")
        let rec Answer(processBM,answer) =
            if (processBM) then Eng.procbm(linestore, !ln, !mvct, answer, !lastmsg, !lastmsg1)
            if (not doAll || !mvct = (!ln).Trim().Split(' ').Length) then
                lnct := !lnct + 1
                mvct := -1
            if (!lnct = vn.Length) then
                Eng.saveLineStore(linestore)
                if (!curdepth = settings.maxdepth) then
                    Send("stop")
                    Send("")
                    //update label
                    let text = "Finished processing variation " + vnname + " to depth " + (!curdepth).ToString()
                    setmsg(text)
                else
                    lnct := 0
                    curdepth := !curdepth + 1
                    //update label
                    let text = "Processing line " + (!lnct+1).ToString() + " of " + vn.Length.ToString() + " for  variation " + vnname + " to depth " + (!curdepth).ToString()
                    setmsg(text)
                    //clear list view for next line
                    clear()
                    //do next one
                    ln := vn.[!lnct].Trim()
                    let mvs = (!ln).Trim().Split(' ')
                    mvct := if doAll then Math.Min(mvs.Length - 1, 5) else mvs.Length
                    if (Eng.alreadyDone(linestore, Eng.str2str(!ln, !mvct), !curdepth)) then
                        Answer(false,"")
                        ComputeAnswer(Eng.str2mvs(!ln, !mvct), !curdepth)
            else
                //update label
                let text = "Processing line " + (!lnct + 1).ToString() + " of " + vn.Length.ToString() + " for  variation " + vnname + " to depth " + (!curdepth).ToString()
                setmsg(text)
                //clear list view for next line
                clear()
                //do next one
                ln := vn.[!lnct].Trim()
                let mvs = (!ln).Trim().Split(' ')
                mvct := if doAll then (if !mvct = -1 then Math.Min(mvs.Length - 1, 5) else !mvct+1) else mvs.Length
                if (Eng.alreadyDone(linestore, Eng.str2str(!ln, !mvct), !curdepth)) then
                    Answer(false,"")
                else
                    ComputeAnswer(Eng.str2mvs(!ln, !mvct), !curdepth)
        let kill(e) =
            if not (proc = null) then
                proc.Kill()
        let p_out(e:System.Diagnostics.DataReceivedEventArgs) =
            if not (e.Data = null||e.Data="") then
                let line = e.Data.ToString().Trim()
                if (settings.log) then Log("--> " + line)
                setinfo(line)
                // ANALYZE THE ANSWER...
                if (line.StartsWith("bestmove")) then
                    let token = line.Split([| ' ' |])
                    if token.Length>1 then
                        Answer(true,token.[1])
                else 
                    lastmsg1 := !lastmsg
                    lastmsg := line


        //on creation do:
        if not (args.Length = 4||args.Length = 5) then
            MessageBox.Show("ERROR: need to pass arguments for whether white, name of variation and whether all or just last move and optionally the line number to analyse, e.g. W KingsIndian ALL") |> ignore
        else
            frm.Text <- "Analysis using " + settings.engine
            //Start process
            proc.StartInfo.CreateNoWindow <- true
            proc.StartInfo.FileName <- Eng.enginepath(settings)
            proc.StartInfo.RedirectStandardOutput <- true
            proc.StartInfo.UseShellExecute <- false
            proc.StartInfo.RedirectStandardInput <- true
            proc.StartInfo.WindowStyle <- System.Diagnostics.ProcessWindowStyle.Hidden
            proc.Start()|>ignore
            proc.BeginOutputReadLine()
            //update messages
            ln := vn.[!lnct].Trim()
            let text = "Processing line " + (!lnct+1).ToString() + " of " + vn.Length.ToString() + " for  variation " + vnname + " to depth " + (!curdepth).ToString()
            setmsg(text)
            let mvs = (!ln).Trim().Split(' ')
            mvct := if doAll then Math.Min(mvs.Length - 1, 5) else mvs.Length
            if (Eng.alreadyDone(linestore, Eng.str2str(!ln, !mvct), !curdepth)) then
                Answer(false,"")
            else
                ComputeAnswer(Eng.str2mvs(!ln, !mvct), !curdepth)

            // add events
            frm.FormClosing.Add(kill)
            proc.OutputDataReceived.Add(p_out)
            helpButton.Click.Add(fun _ -> Help.ShowHelp(frm,Path.Combine(Application.StartupPath, "LizardChess.chm"), HelpNavigator.Topic, "EngineAnalyser.htm"))
        
        frm
    [<STAThread>]
    do Application.Run(form)